package com.gitee.wsl.ext.base

import kotlin.jvm.JvmStatic
import kotlin.random.Random

typealias Character = Char

val Char.Companion.SIZE: Int
    get() = Char.SIZE_BITS

val Char.Companion.BYTES: Int
    get() = Char.SIZE_BYTES


val Char.Companion.typography get() = Typography

val Char.Companion.A_Z: CharRange
    get() = 'A'..'Z'

val Char.Companion.a_z: CharRange
    get() = 'a'..'z'

val Char.Companion.C0_9: CharRange
    get() = '0'..'9'

val Char.Companion.a_Z: List<Char>
    get() = a_z + A_Z

val Char.Companion.BaseLatter: List<Char>
    get() = a_z + A_Z + C0_9

fun Char.propCase(char: Char) = if (char.isLowerCase()) lowercaseChar() else uppercaseChar()


object CharExt {
    //val  A_Z = 'A'..'Z'
    //val  a_z = 'a'..'z'
    //val  C0_9 = '0'..'9'
    //val  base = Char.a_Z + Char.C0_9

    /**
     * Repeats text multiple times
     *
     * @param c     character to repeat
     * @param times number of times. Values less or equal to zero will result in empty string
     * @return text repeated multiple times
     */
    @JvmStatic
    fun repeatChar(c: Char, times: Int): String {
        if (times < 0) {
            return ""
        }
        val result = CharArray(times)
        result.fill(c)

        return result.concatToString()
    }

    /**
     * 是否为ASCII字符，ASCII字符位于0~127之间
     *
     * <pre>
     * CharUtil.isAscii('a')  = true
     * CharUtil.isAscii('A')  = true
     * CharUtil.isAscii('3')  = true
     * CharUtil.isAscii('-')  = true
     * CharUtil.isAscii('\n') = true
     * CharUtil.isAscii('') = false
    </pre> *
     *
     * @param ch 被检查的字符处
     * @return true表示为ASCII字符，ASCII字符位于0~127之间
     */
/*    fun isAscii(ch: Char): Boolean {
        return ch.code < 128
    }*/

    /**
     * 是否为可见ASCII字符，可见字符位于32~126之间
     *
     * <pre>
     * CharUtil.isAsciiPrintable('a')  = true
     * CharUtil.isAsciiPrintable('A')  = true
     * CharUtil.isAsciiPrintable('3')  = true
     * CharUtil.isAsciiPrintable('-')  = true
     * CharUtil.isAsciiPrintable('\n') = false
     * CharUtil.isAsciiPrintable('') = false
    </pre> *
     *
     * @param ch 被检查的字符处
     * @return true表示为ASCII可见字符，可见字符位于32~126之间
     */
/*    fun isAsciiPrintable(ch: Char): Boolean {
        return ch.code in 32..126
    }*/

    /**
     * 是否为ASCII控制符（不可见字符），控制符位于0~31和127
     *
     * <pre>
     * CharUtil.isAsciiControl('a')  = false
     * CharUtil.isAsciiControl('A')  = false
     * CharUtil.isAsciiControl('3')  = false
     * CharUtil.isAsciiControl('-')  = false
     * CharUtil.isAsciiControl('\n') = true
     * CharUtil.isAsciiControl('') = false
    </pre> *
     *
     * @param ch 被检查的字符
     * @return true表示为控制符，控制符位于0~31和127
     */
/*    fun isAsciiControl(ch: Char): Boolean {
        return ch.code < 32 || ch.code == 127
    }*/

    /**
     * 判断是否为字母（包括大写字母和小写字母）<br></br>
     * 字母包括A~Z和a~z
     *
     * <pre>
     * CharUtil.isLetter('a')  = true
     * CharUtil.isLetter('A')  = true
     * CharUtil.isLetter('3')  = false
     * CharUtil.isLetter('-')  = false
     * CharUtil.isLetter('\n') = false
     * CharUtil.isLetter('') = false
    </pre> *
     *
     * @param ch 被检查的字符
     * @return true表示为字母（包括大写字母和小写字母）字母包括A~Z和a~z
     */
/*    fun isLetter(ch: Char): Boolean {
        return isLetterUpper(ch) || isLetterLower(ch)
    }*/

    /**
     *
     *
     * 判断是否为大写字母，大写字母包括A~Z
     *
     *
     * <pre>
     * CharUtil.isLetterUpper('a')  = false
     * CharUtil.isLetterUpper('A')  = true
     * CharUtil.isLetterUpper('3')  = false
     * CharUtil.isLetterUpper('-')  = false
     * CharUtil.isLetterUpper('\n') = false
     * CharUtil.isLetterUpper('') = false
    </pre> *
     *
     * @param ch 被检查的字符
     * @return true表示为大写字母，大写字母包括A~Z
     */
/*    fun isLetterUpper(ch: Char): Boolean {
        return ch in 'A'..'Z'
    }*/

    /**
     *
     *
     * 检查字符是否为小写字母，小写字母指a~z
     *
     *
     * <pre>
     * CharUtil.isLetterLower('a')  = true
     * CharUtil.isLetterLower('A')  = false
     * CharUtil.isLetterLower('3')  = false
     * CharUtil.isLetterLower('-')  = false
     * CharUtil.isLetterLower('\n') = false
     * CharUtil.isLetterLower('') = false
    </pre> *
     *
     * @param ch 被检查的字符
     * @return true表示为小写字母，小写字母指a~z
     */
/*    fun isLetterLower(ch: Char): Boolean {
        return ch in 'a'..'z'
    }*/

    /**
     *
     *
     * 检查是否为数字字符，数字字符指0~9
     *
     *
     * <pre>
     * CharUtil.isNumber('a')  = false
     * CharUtil.isNumber('A')  = false
     * CharUtil.isNumber('3')  = true
     * CharUtil.isNumber('-')  = false
     * CharUtil.isNumber('\n') = false
     * CharUtil.isNumber('') = false
    </pre> *
     *
     * @param ch 被检查的字符
     * @return true表示为数字字符，数字字符指0~9
     */
/*    fun isNumber(ch: Char): Boolean {
        return ch in '0'..'9'
    }*/

    /**
     * 是否为16进制规范的字符，判断是否为如下字符
     * <pre>
     * 1. 0~9
     * 2. a~f
     * 4. A~F
    </pre> *
     *
     * @param c 字符
     * @return 是否为16进制规范的字符
     * @since 4.1.5
     */
/*    fun isHexChar(c: Char): Boolean {
        return isNumber(c) || c in 'a'..'f' || c in 'A'..'F'
    }*/

    /**
     * 是否为字母或数字，包括A~Z、a~z、0~9
     *
     * <pre>
     * CharUtil.isLetterOrNumber('a')  = true
     * CharUtil.isLetterOrNumber('A')  = true
     * CharUtil.isLetterOrNumber('3')  = true
     * CharUtil.isLetterOrNumber('-')  = false
     * CharUtil.isLetterOrNumber('\n') = false
     * CharUtil.isLetterOrNumber('') = false
    </pre> *
     *
     * @param ch 被检查的字符
     * @return true表示为字母或数字，包括A~Z、a~z、0~9
     */
/*    fun isLetterOrNumber(ch: Char): Boolean {
        return isLetter(ch) || isNumber(ch)
    }*/


    /**
     * 给定类名是否为字符类，字符类包括：
     *
     * <pre>
     * Character.class
     * char.class
    </pre> *
     *
     * @param clazz 被检查的类
     * @return true表示为字符类
     */
/*    fun isCharClass(clazz: Class<*>): Boolean {
        return clazz == Char::class.java || clazz == Char::class.javaPrimitiveType
    }*/

    /**
     * 给定对象对应的类是否为字符类，字符类包括：
     *
     * <pre>
     * Character.class
     * char.class
    </pre> *
     *
     * @param value 被检查的对象
     * @return true表示为字符类
     */
/*    fun isChar(value: Any): Boolean {
        return value is Char || value.javaClass == Char::class.javaPrimitiveType
    }*/

    /**
     * 是否空白符<br></br>
     * 空白符包括空格、制表符、全角空格和不间断空格<br></br>
     *
     * @param c 字符
     * @return 是否空白符
     * @see Character.isWhitespace
     * @see Character.isSpaceChar
     * @since 4.0.10
     */
/*    fun isBlankChar(c: Char): Boolean {
        return isBlankChar(c.code)
    }*/

    /**
     * 是否空白符<br></br>
     * 空白符包括空格、制表符、全角空格和不间断空格<br></br>
     *
     * @param c 字符
     * @return 是否空白符
     * @see Character.isWhitespace
     * @see Character.isSpaceChar
     * @since 4.0.10
     */
/*    fun isBlankChar(c: Int): Boolean {
        return (Character.isWhitespace(c) || Character.isSpaceChar(c)) || c == '\ufeff'.code || c == '\u202a'.code || c == '\u0000'.code || c == '\u3164'.code || c == '\u2800'.code || c == '\u180e'.code
    }*/

    /**
     * 判断是否为emoji表情符<br></br>
     *
     * @param c 字符
     * @return 是否为emoji
     * @since 4.0.8
     */
    fun isEmoji(c: Char): Boolean {
        return !(c.code == 0x0 ||
                c.code == 0x9 ||
                c.code == 0xA ||
                c.code == 0xD ||
                (c.code in 0x20..0xD7FF) ||
                (c.code in 0xE000..0xFFFD) ||
                (c.code in 0x100000..0x10FFFF))
    }

    /**
     * 是否为Windows或者Linux（Unix）文件分隔符<br></br>
     * Windows平台下分隔符为\，Linux（Unix）为/
     *
     * @param c 字符
     * @return 是否为Windows或者Linux（Unix）文件分隔符
     * @since 4.1.11
     */
    fun isFileSeparator(c: Char): Boolean {
        return '\\' == c || '/' == c
    }

    /**
     * 比较两个字符是否相同
     *
     * @param c1              字符1
     * @param c2              字符2
     * @param caseInsensitive 是否忽略大小写
     * @return 是否相同
     * @since 4.0.3
     */
    fun equals(c1: Char, c2: Char, caseInsensitive: Boolean): Boolean {
        return if (caseInsensitive) {
            c1.lowercaseChar() == c2.lowercaseChar()
        } else c1 == c2
    }

    /**
     * 获取字符类型
     *
     * @param c 字符
     * @return 字符类型
     * @since 5.2.3
     */
/*    fun getType(c: Int): Int {
        return Character.getType(c)
    }*/

    /**
     * 获取给定字符的16进制数值
     *
     * @param b 字符
     * @return 16进制字符
     * @since 5.3.1
     */
/*    fun digit16(b: Int): Int {
        return Character.digit(b, 16)
    }*/

    /**
     * 将字母、数字转换为带圈的字符：
     * <pre>
     * '1' -》 '①'
     * 'A' -》 'Ⓐ'
     * 'a' -》 'ⓐ'
    </pre> *
     *
     *
     * 获取带圈数字 /封闭式字母数字 ，从1-20,超过1-20报错
     *
     * @param c 被转换的字符，如果字符不支持转换，返回原字符
     * @return 转换后的字符
     * @see [Unicode_symbols](https://en.wikipedia.org/wiki/List_of_Unicode_characters.Unicode_symbols)
     *
     * @see [Alphanumerics](https://en.wikipedia.org/wiki/Enclosed_Alphanumerics)
     *
     * @since 5.6.2
     */
    fun toCloseChar(c: Char): Char {
        var result = c.code
        when (c) {
            in '1'..'9' -> {
                result = '①'.code + c.code - '1'.code
            }
            in 'A'..'Z' -> {
                result = 'Ⓐ'.code + c.code - 'A'.code
            }
            in 'a'..'z' -> {
                result = 'ⓐ'.code + c.code - 'a'.code
            }
        }
        return result.toChar()
    }

    /**
     * 将[1-20]数字转换为带圈的字符：
     * <pre>
     * 1 -》 '①'
     * 12 -》 '⑫'
     * 20 -》 '⑳'
    </pre> *
     * 也称作：封闭式字符，英文：Enclosed Alphanumerics
     *
     * @param number 被转换的数字
     * @return 转换后的字符
     * @author dazer
     * @see [维基百科wikipedia-Unicode_symbols](https://en.wikipedia.org/wiki/List_of_Unicode_characters.Unicode_symbols)
     *
     * @see [维基百科wikipedia-Unicode字符列表](https://zh.wikipedia.org/wiki/Unicode%E5%AD%97%E7%AC%A6%E5%88%97%E8%A1%A8)
     *
     * @see [coolsymbol](https://coolsymbol.com/)
     *
     * @see [百度百科 特殊字符](https://baike.baidu.com/item/%E7%89%B9%E6%AE%8A%E5%AD%97%E7%AC%A6/112715?fr=aladdin)
     *
     * @since 5.6.2
     */
    fun toCloseByNumber(number: Int): Char {
        require(number <= 20) { "Number must be [1-20]" }
        return ('①'.code + number - 1).toChar()
    }

    /**
     * unicode 最大码点为 0x10FFFF http://www.unicode.org/L2/L2000/00079-n2175.htm
     * 所以用0xFFFFFFFF 表示读取到了结尾
     */
    const val EOF = Int.MAX_VALUE

    fun toUnicodeString(code: Int): String {
        return if (code == EOF) {
            "EOF"
        } else {
            code.toString()
        }
    }

    fun isLetterCapitalCase(code: Int): Boolean {
        return code in 0x41..0x5A
    }

    fun isLetterSmallCase(code: Int): Boolean {
        return code in 0x61..0x7A
    }

    /**
     * Converts the specified character (Unicode code point) to its
     * UTF-16 representation stored in a char array.
     * If
     * the specified code point is a BMP (Basic Multilingual Plane or
     * Plane 0) value, the resulting char array has
     * the same value as [codePoint].
     * If the specified code
     * point is a supplementary code point, the resulting
     * char array has the corresponding surrogate pair.
     *
     * @param  codePoint a Unicode code point
     * @return a char array having
     *         codePoint's UTF-16 representation.
     * @throws IllegalArgumentException if the specified
     * {@code codePoint} is not a valid Unicode code point.
     */
    fun toChars(codePoint: Int): CharArray {
        return if (isBmpCodePoint(codePoint)) {
            charArrayOf(codePoint.toChar())
        } else if (isValidCodePoint(codePoint)) {
            val result = CharArray(2)
            toSurrogates(codePoint, result, 0)
            result
        } else {
            throw IllegalArgumentException()
        }
    }

    /**
     * Determines whether the specified character (Unicode code point)
     * is in the <a href="#BMP">Basic Multilingual Plane (BMP)</a>.
     * Such code points can be represented using a single {@code char}.
     *
     * @param  codePoint the character (Unicode code point) to be tested
     * @return {@code true} if the specified code point is between
     *         {@link #MIN_VALUE} and {@link #MAX_VALUE} inclusive;
     *         {@code false} otherwise.
     */
    private fun isBmpCodePoint(codePoint: Int): Boolean {
        return codePoint ushr 16 == 0
        // Optimized form of:
        //     codePoint >= MIN_VALUE && codePoint <= MAX_VALUE
        // We consistently use logical shift (>>>) to facilitate
        // additional runtime optimizations.
    }

    /**
     * Determines whether the specified code point is a valid
     * [
 * Unicode code point value](http://www.unicode.org/glossary/#code_point).
     *
     * @param  codePoint the Unicode code point to be tested
     * @return `true` if the specified code point value is between
     * [Char.MIN_VALUE] and
     * [Char.MAX_VALUE] inclusive;
     * `false` otherwise.
     */
    private fun isValidCodePoint(codePoint: Int): Boolean {
        // Optimized form of:
        //     codePoint >= MIN_CODE_POINT && codePoint <= MAX_CODE_POINT
        val plane = codePoint ushr 16
        return plane < ((MAX_CODE_POINT + 1) ushr 16)
    }

    private fun toSurrogates(codePoint: Int, dst: CharArray, index: Int) {
        // We write elements "backwards" to guarantee all-or-nothing
        dst[index + 1] = lowSurrogate(codePoint)
        dst[index] = highSurrogate(codePoint)
    }

    /**
     * Returns the leading surrogate (a
     * <a href="http://www.unicode.org/glossary/#high_surrogate_code_unit">high surrogate code unit</a> of the
     * <a href="http://www.unicode.org/glossary/#surrogate_pair">surrogate</a>
     * representing the specified supplementary character (Unicode
     * code point) in the UTF-16 encoding.  If the specified character
     * is not a supplementary character,
     * an unspecified `char` is returned.
     *
     *
     * If
     * [isSupplementaryCodePoint(x)][isSupplementaryCodePoint]
     * is `true`, then
     * [isHighSurrogate][.isHighSurrogate]`(highSurrogate(x))` and
     * [toCodePoint][.toCodePoint]`(highSurrogate(x), `[lowSurrogate][.lowSurrogate]`(x)) == x`
     * are also always `true`.
     *
     * @param   codePoint a supplementary character (Unicode code point)
     * @return  the leading surrogate code unit used to represent the
     * character in the UTF-16 encoding
     */
    private fun highSurrogate(codePoint: Int): Char {
        return ((codePoint ushr 10)
                + (Char.MIN_HIGH_SURROGATE.code - (MIN_SUPPLEMENTARY_CODE_POINT ushr 10))).toChar()
    }

    /**
     * Returns the trailing surrogate (a
     * [
 * low surrogate code unit](http://www.unicode.org/glossary/#low_surrogate_code_unit)) of the
     * [
 * surrogate pair](http://www.unicode.org/glossary/#surrogate_pair)
     * representing the specified supplementary character (Unicode
     * code point) in the UTF-16 encoding.  If the specified character
     * is not a
     * [supplementary character](Character.html#supplementary),
     * an unspecified `char` is returned.
     *
     *
     * If
     * [isSupplementaryCodePoint(x)][.isSupplementaryCodePoint]
     * is `true`, then
     * [isLowSurrogate][.isLowSurrogate]`(lowSurrogate(x))` and
     * [toCodePoint][.toCodePoint]`(`[highSurrogate][.highSurrogate]`(x), lowSurrogate(x)) == x`
     * are also always `true`.
     *
     * @param   codePoint a supplementary character (Unicode code point)
     * @return  the trailing surrogate code unit used to represent the
     * character in the UTF-16 encoding
     */
    private fun lowSurrogate(codePoint: Int): Char {
        return ((codePoint and 0x3ff) + Char.MIN_LOW_SURROGATE.code).toChar()
    }

    /**
     * The minimum value of a
     * [Unicode supplementary code point](http://www.unicode.org/glossary/#supplementary_code_point), constant `U+10000`.
     */
    private const val MIN_SUPPLEMENTARY_CODE_POINT = 0x010000

    /**
     * The maximum value of a
     * <a href="http://www.unicode.org/glossary/#code_point">
     * Unicode code point</a>, constant {@code U+10FFFF}.
     */
    private const val MAX_CODE_POINT = 0X10FFFF

}

val Char.Companion.MIN_RADIX: Int
    get() = 2

val Char.Companion.MAX_RADIX: Int
    get() = 36


/**
 * 字符常量：空格符 `' '`
 */
val Char.Companion.SPACE: Char
    get() = ' '

/**
 * 字符常量：制表符 `'\t'`
 */
val Char.Companion.TAB: Char
    get() = '	'

/**
 * 字符常量：点 `'.'`
 */
val Char.Companion.DOT: Char
    get() = '.'

/**
 * 字符常量：斜杠 `'/'`
 */
val Char.Companion.SLASH: Char
    get() = '/'

/**
 * 字符常量：反斜杠 `'\\'`
 */
val Char.Companion.BACKSLASH: Char
    get() = '\\'

/**
 * 字符常量：回车符 `'\r'`
 */
val Char.Companion.CR: Char
    get() = '\r'

/**
 * 字符常量：换行符 `'\n'`
 */
val Char.Companion.LF: Char
    get() = '\n'

/**
 * 字符常量：减号（连接符） `'-'`
 */
val Char.Companion.DASHED: Char
    get() = '-'

/**
 * 字符常量：下划线 `'_'`
 */
val Char.Companion.UNDERLINE: Char
    get() = '_'

/**
 * 字符常量：逗号 `','`
 */
val Char.Companion.COMMA: Char
    get() = ','

/**
 * 字符常量：花括号（左） `'{'`
 */
val Char.Companion.DELIM_START: Char
    get() = '{'

/**
 * 字符常量：花括号（右） `'}'`
 */
val Char.Companion.DELIM_END: Char
    get() = '}'

/**
 * 字符常量：中括号（左） `'['`
 */
val Char.Companion.BRACKET_START: Char
    get() = '['

/**
 * 字符常量：中括号（右） `']'`
 */
val Char.Companion.BRACKET_END: Char
    get() = ']'

/**
 * 字符常量：双引号 `'"'`
 */
val Char.Companion.DOUBLE_QUOTES: Char
    get() = '"'

/**
 * 字符常量：单引号 `'\''`
 */
val Char.Companion.SINGLE_QUOTE: Char
    get() = '\''

/**
 * 字符常量：与 `'&'`
 */
val Char.Companion.AMP: Char
    get() = '&'

/**
 * 字符常量：冒号 `':'`
 */
val Char.Companion.COLON: Char
    get() = ':'

/**
 * 字符常量：艾特 `'@'`
 */
val Char.Companion.AT: Char
    get() = '@'

fun Char.isBaseChar(vararg expChar:Char) =  !expChar.contains(this) && Char.BaseLatter.contains(this)

/**
 * Checks if a Char is equal to '0'.
 *
 * @return `true` if the Char is '0', `false` otherwise.
 */
fun Char.isZero(): Boolean = this == '0'


/** 将 [Char] 转换为 16 进制 [String] 对应的 [Byte] */
fun Char.toHexByte(): Byte {
    return "0123456789ABCDEF".indexOf(this).toByte()
}

val Char.fullWidth
    get() =
        when (code) {
            32 -> 12_288.toChar()
            in 33..126 -> (code + 65_248).toChar()
            else -> this
        }

val Char.halfWidth
    get() =
        when (code) {
            12_288 -> 32.toChar()
            in 65_281..65_374 -> (code - 65_248).toChar()
            else -> this
        }


/**
 * Checks if the character is a vowel (a, e, i, o, u).
 *
 * @return true if the character is a vowel, false otherwise.
 */
fun Char.isVowel(): Boolean {
    val ae = this == 'a' || this == 'e'
    val io = this == 'i' || this == 'o'
    val iou = io || this == 'u'
    return ae || iou
}

val Char.isAlphabetic
    get() = isLetter()

val Char.isHexChar: Boolean
    get() = isDigit() || this in 'a'..'f' || this in 'A'..'F'

val Char.isAscii get() = code < 128

val Char.toCloseChar get() = CharExt.toCloseChar(this)

val Char.isAsciiControl get() = code < 32 || code == 127

val Char.isAsciiPrintable get() = code in 32..126

val Char.isEmoji get() = CharExt.isEmoji(this)

val Char.isIdentifierStart: Boolean get() {
    if (this in 'a'..'z') {
        return true
    }
    if (this in 'A'..'Z') {
        return true
    }
    if (this == '_') {
        return true
    }
    return false
}

fun Char.isLineBreak(): Boolean {
    val stringChar = this.toString()
    return stringChar == "\n" || stringChar == "\r" || stringChar == "\r\n"
}

fun Char.isDelim() = "~!@#$%^&*()-=+[]{}|;:,.<>/?".contains(this)

// Kotlin complains to replace char comparisons with range checks. Tested, with range checks it's a bit slower
// (up to 10-15%). Negligible, but keeping the old code, as it has more obvious performance. Range checks rely
// on some implicit optimizations (Kotlin compiler or JVM), apparently.
@Suppress("ConvertTwoComparisonsToRangeCheck")
fun Char.isHexDigit( ) = isDigit() || this >= 'A' && this <= 'F' || this >= 'a' && this <= 'f'

/**
 * Generates a random string of the specified length consisting of characters within the closed range.
 *
 * @param length the length of the generated random string.
 * @return a random string of the specified length.
 */
fun ClosedRange<Char>.randomString(length: Int) =
    (1..length)
        .map {
            val randomLimit = endInclusive.code - start.code
            val randomCode = Random.nextInt(until = randomLimit) + start.code
            randomCode.toChar()
        }
        .joinToString("")

fun Char.isSpaceChar(): Boolean = this == Char.SPACE

/**
 * 是否空白符<br></br>
 * 空白符包括空格、制表符、全角空格和不间断空格<br></br>
 *
 * @param c 字符
 * @return 是否空白符
 * @see Character.isWhitespace
 * @see Character.isSpaceChar
 * @since 4.0.10
 */
fun Char.isBlankChar( ): Boolean {
    val c = code
    return isWhitespace()
            || isSpaceChar()
            || c == '\ufeff'.code || c == '\u202a'.code || c == '\u0000'.code // issue#I5UGSQ，Hangul Filler
            || c == '\u3164'.code // Braille Pattern Blank
            || c == '\u2800'.code // Zero Width Non-Joiner, ZWNJ
            || c == '\u200c'.code // MONGOLIAN VOWEL SEPARATOR
            || c == '\u180e'.code
}

